package log

import (
	"errors"
	"fmt"
	time2 "leaf/core/time"
	"log"
	"os"
	"path"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

// levels
const (
	debugLevel   = 0
	releaseLevel = 1
	errorLevel   = 2
	fatalLevel   = 3
)

const (
	printDebugLevel   = "[debug  ]"
	printReleaseLevel = "[release]"
	printErrorLevel   = "[error  ]"
	printFatalLevel   = "[fatal  ]"
)

type Logger struct {
	level      int
	baseLogger *log.Logger
	baseFile   *os.File
	path       string
	header     string
	timeStamp  int64
	flag       int
	fileFlag   int32
	logChan    chan string
	newChan    chan int32
	maxSize    int64
	sigChan    chan bool
	sync.WaitGroup
}

func New(strLevel string, pathname string, header string, maxSize int64, flag int) (*Logger, error) {
	// level
	var level int
	switch strings.ToLower(strLevel) {
	case "debug":
		level = debugLevel
	case "release":
		level = releaseLevel
	case "error":
		level = errorLevel
	case "fatal":
		level = fatalLevel
	default:
		return nil, errors.New("unknown level: " + strLevel)
	}

	// logger
	logger := new(Logger)
	logger.level = level
	logger.path = pathname
	logger.header = header
	logger.timeStamp = time2.TimeStamp
	logger.flag = flag
	logger.logChan = make(chan string, 1024)
	logger.newChan = make(chan int32, 1)
	logger.maxSize = 1024 * 1024 * maxSize
	logger.sigChan = make(chan bool, 1)
	logger.newFile()
	go logger.run()

	return logger, nil
}

func (logger *Logger) newFile() {
	if logger.baseFile != nil {
		logger.baseFile.Close()
		logger.baseLogger = nil
		logger.baseFile = nil
	}

	if logger.path != "" {
		now := time.Now()

		filename := fmt.Sprintf("%s_%d%02d%02d%02d%02d%02d.log",
			logger.header,
			now.Year(),
			now.Month(),
			now.Day(),
			now.Hour(),
			now.Minute(),
			now.Second())

		file, err := os.OpenFile(path.Join(logger.path, filename), os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
		if err != nil {
			return
		}

		logger.baseLogger = log.New(file, "", logger.flag)
		logger.baseFile = file
	} else {
		logger.baseLogger = log.New(os.Stdout, "", logger.flag)
	}

}

func (logger *Logger) Close() {
	close(logger.sigChan)
	logger.Wait()

	if logger.baseFile != nil {
		logger.baseFile.Close()
	}
	logger.baseLogger = nil
	logger.baseFile = nil

	close(logger.logChan)
	close(logger.newChan)
}

func (logger *Logger) needNewFile() int32 {
	var timeStamp = time2.TimeStamp
	if int32(logger.timeStamp/3600) != int32(timeStamp/3600) {
		logger.timeStamp = timeStamp
		return 1
	}
	if fi, err := logger.baseFile.Stat(); err != nil {
		return 2
	} else if fi.Size() > logger.maxSize {
		return 3
	}
	return 0
}

func (logger *Logger) run() {
	logger.Add(1)
	defer logger.Add(-1)
	for {
		var logStr string
		select {
		case <-logger.newChan:
			logger.newFile()
		case logStr = <-logger.logChan:
			fmt.Println(logStr)
			logger.baseLogger.Output(3, logStr)
		case <-logger.sigChan:
			for i := 0; i < len(logger.logChan); i++ {
				logStr = <-logger.logChan
				fmt.Println(logStr)
				logger.baseLogger.Output(3, logStr)
			}
			return
		}
	}
}

func (logger *Logger) doPrintf(level int, printLevel string, format string, a ...interface{}) {
	if level < logger.level {
		return
	}
	if logger.baseLogger == nil && logger.logChan == nil {
		panic("logger closed")
	}

	if needNew := logger.needNewFile(); needNew != 0 {
		if atomic.CompareAndSwapInt32(&logger.fileFlag, 0, 1) {
			atomic.CompareAndSwapInt32(&logger.fileFlag, 1, 0)
			logger.newChan <- needNew
		}
	}

	//format = printLevel + format

	var prefix string
	_, file, line, ok := runtime.Caller(2)
	if ok {
		i := strings.LastIndex(file, "src/") + 4
		prefix = fmt.Sprintf("%s[%s][%s:%d]:", printLevel, time2.Date(), (string)(([]byte(file))[i:]), line)
	}
	format = prefix + format
	format = fmt.Sprintf(format, a...)
	logger.logChan <- format
	if level == fatalLevel {
		logger.Close()
		os.Exit(1)
	}
}

func (logger *Logger) Debug(format string, a ...interface{}) {
	logger.doPrintf(debugLevel, printDebugLevel, format, a...)
}

func (logger *Logger) Release(format string, a ...interface{}) {
	logger.doPrintf(releaseLevel, printReleaseLevel, format, a...)
}

func (logger *Logger) Error(format string, a ...interface{}) {
	logger.doPrintf(errorLevel, printErrorLevel, format, a...)
}

func (logger *Logger) Fatal(format string, a ...interface{}) {
	logger.doPrintf(fatalLevel, printFatalLevel, format, a...)
}

//var gLogger *Logger

var gLogger, _ = New("debug", "", "", 80, log.LstdFlags)

// It's dangerous to call the method on logging
func Export(logger *Logger) {
	if logger != nil {
		gLogger = logger
	}
}

func Debug(format string, a ...interface{}) {
	gLogger.doPrintf(debugLevel, printDebugLevel, format, a...)
}

func Release(format string, a ...interface{}) {
	gLogger.doPrintf(releaseLevel, printReleaseLevel, format, a...)
}

func Error(format string, a ...interface{}) {
	gLogger.doPrintf(errorLevel, printErrorLevel, format, a...)
}

func Fatal(format string, a ...interface{}) {
	gLogger.doPrintf(fatalLevel, printFatalLevel, format, a...)
}

func Close() {
	gLogger.Close()
}
